bởi Gabriel L. Manor Supabase giúp dễ dàng thêm xác thực vào ứng dụng của bạn với hỗ trợ tích hợp cho email, OAuth và liên kết ma thuật.Nhưng trong khi Supabase Auth xử lý người dùng của bạn, bạn thường cần một lớp ủy quyền cũng vậy. Supabase cung cấp một backend tuyệt vời với built-in auth và Row Level Security (RLS), quản lý Đặc biệt là những người dựa trên - Quá xa là dễ dàng. fine-grained permissions relationships between users and data Bạn có thể muốn hạn chế các hành động như chỉnh sửa hoặc xóa dữ liệu cho chủ sở hữu tài nguyên, ngăn người dùng bỏ phiếu về nội dung của riêng họ, hoặc thực thi các quyền khác nhau cho vai trò người dùng khác nhau. Hướng dẫn này đi qua làm thế nào để thực hiện trong a Ứng dụng Supabase authentication and authorization Next.js Chúng ta sẽ bắt đầu với cho login và quản lý phiên, sau đó thêm using , áp dụng thông qua và a của Supabase Auth authorization rules Kiểm soát truy cập dựa trên mối quan hệ (ReBAC) Supabase Edge Functions local Policy Decision Point (PDP) Cuối cùng, bạn sẽ có một ứng dụng khảo sát cộng tác thời gian thực hỗ trợ cả hành động công khai và được bảo vệ - và một hệ thống ủy quyền linh hoạt mà bạn có thể phát triển khi ứng dụng của bạn phát triển. Những gì chúng tôi đang xây Trong hướng dẫn này, chúng tôi sẽ xây dựng một ứng dụng thăm dò thời gian thực bằng cách sử dụng và Điều này cho thấy cả xác thực và ủy quyền trong hành động. Supabase Next.js Ứng dụng cho phép người dùng tạo các cuộc thăm dò, bỏ phiếu cho người khác và chỉ quản lý nội dung của riêng họ. cho login / signup và làm thế nào để thực thi điều khiển người có thể bỏ phiếu, chỉnh sửa hoặc xóa. Supabase Auth authorization policies Chúng tôi sẽ sử dụng các tính năng cốt lõi của Supabase - , , , và - Kết hợp với a mô hình để thực thi các quy tắc truy cập per user và per resource. Auth Postgres RLS Realtime Edge Functions Relationship-Based Access Control (ReBAC) Công nghệ Stack Thì Thì Thì Thì Thì Supabase – Backend-as-a-service cho cơ sở dữ liệu, xác thực, thời gian thực và các chức năng cạnh Next.js – Framework Frontend để xây dựng ứng dụng UI và API routes Permit.io – (đối với ReBAC) để xác định và đánh giá logic ủy quyền thông qua PDP Supabase CLI – Để quản lý và triển khai các chức năng Edge tại địa phương và trong sản xuất Cơ sở Tiếp theo.js Cho phép.io Đánh giá CLI Điều kiện Thì Thì Thì Thì Thì Thì Node.js đã cài đặt Tài khoản Supabase account Permit.io Giới thiệu về React/Next.js Dự án Repo App này có thể làm gì? Ứng dụng demo là một nền tảng thăm dò thời gian thực được xây dựng với Next.js và Supabase, nơi người dùng có thể tạo các cuộc thăm dò và bỏ phiếu cho người khác. Thì Thì Thì Thì Thì Bất kỳ người dùng nào (được xác thực hay không) có thể xem danh sách các cuộc thăm dò công khai Chỉ người dùng được xác thực mới có thể tạo các cuộc thăm dò và bỏ phiếu Một người dùng không thể bỏ phiếu trên một cuộc thăm dò mà họ đã tạo Only the can edit or delete it creator of a poll Tutorial tổng quan Chúng tôi sẽ làm theo các bước chung này: Thì Thì Thì Thì Thì Thì Thiết lập dự án Supabase, Schema, Auth và RLS Xây dựng các tính năng cốt lõi của ứng dụng như tạo cuộc thăm dò và bỏ phiếu Quy tắc ủy quyền mô hình xác định vai trò và quy tắc trong Permit.io Tạo chức năng Supabase Edge để đồng bộ người dùng, gán vai trò và kiểm tra quyền Thực thi các chính sách trong frontend ứng dụng bằng cách sử dụng các hàm Edge đó Hãy bắt đầu - Setting up Supabase in the Project Thiết lập Supabase trong dự án Tùy chọn: Clone the Starter Template Tôi đã tạo ra một Có với tất cả các mã bạn cần để bắt đầu để chúng tôi có thể tập trung vào việc thực hiện Supabase và Permit.io. Bắt đầu template GitHub Bạn có thể clone dự án bằng cách chạy lệnh sau: git clone <https://github.com/permitio/supabase-fine-grained-authorization> Sau khi bạn đã nhân bản dự án, hãy điều hướng đến thư mục dự án và cài đặt các phụ thuộc: cd realtime-polling-app-nextjs-supabase-permitio npm install Tạo một dự án mới trong Supabase Để bắt đầu: Thì Thì Thì Thì Đi đến https://supabase.com và đăng nhập hoặc tạo tài khoản. Nhấp vào "New Project" và điền tên dự án, mật khẩu và khu vực của bạn. Sau khi tạo, hãy đi đến Thiết đặt dự án → API và lưu ý URL dự án của bạn và Anon Key – bạn sẽ cần chúng sau. Thiết lập xác thực và cơ sở dữ liệu trong Supabase Chúng tôi sẽ sử dụng email/password auth tích hợp của Supabase: Thì Thì Thì Thì Trong thanh bên, đi đến xác thực → Nhà cung cấp Cho phép nhà cung cấp email (Tùy chọn) Tắt email xác nhận để kiểm tra, nhưng giữ cho nó được bật cho sản xuất Tạo Database Schema Ứng dụng này sử dụng ba bảng chính: , và Sử dụng the trong bảng điều khiển Supabase và chạy như sau: polls options votes SQL Editor -- Create a polls table CREATE TABLE polls ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, question TEXT NOT NULL, created_by UUID REFERENCES auth.users(id) NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), creator_name TEXT NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, ); -- Create an options table CREATE TABLE options ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, text TEXT NOT NULL, ); -- Create a votes table CREATE TABLE votes ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, poll_id UUID REFERENCES polls(id) ON DELETE CASCADE, option_id UUID REFERENCES options(id) ON DELETE CASCADE, user_id UUID REFERENCES auth.users(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()), UNIQUE(poll_id, user_id) ); Bảo mật cấp hàng (Row Level Security - RLS) cho phép Đối với mỗi bảng và xác định chính sách: RLS -- Polls policies ALTER TABLE polls ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view polls" ON polls FOR SELECT USING (true); CREATE POLICY "Authenticated users can create polls" ON polls FOR INSERT TO authenticated WITH CHECK (auth.uid() = created_by); -- Options policies ALTER TABLE options ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view options" ON options FOR SELECT USING (true); CREATE POLICY "Poll creators can add options" ON options FOR INSERT TO authenticated WITH CHECK ( EXISTS ( SELECT 1 FROM polls WHERE id = options.poll_id AND created_by = auth.uid() ) ); -- Votes policies ALTER TABLE votes ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view votes" ON votes FOR SELECT USING (true); CREATE POLICY "Authenticated users can vote once" ON votes FOR INSERT TO authenticated WITH CHECK ( auth.uid() = user_id AND NOT EXISTS ( SELECT 1 FROM polls WHERE id = votes.poll_id AND created_by = auth.uid() ) ); Để sử dụng các tính năng thời gian thực của Supabase: Thì Thì Thì Trong thanh bên, đi đến Table Editor Đối với mỗi trong ba bảng (các cuộc thăm dò, tùy chọn, phiếu bầu): Nhấp vào ba điểm → Sửa bảng Toggle "Enable Realtime" Lưu các thay đổi Thực hiện Supabase Email Authentication trong ứng dụng Trong ứng dụng demo này, bất cứ ai cũng có thể xem danh sách các cuộc thăm dò có sẵn trên ứng dụng, cả hoạt động và hết hạn. Để xem chi tiết của một cuộc thăm dò, quản lý hoặc bỏ phiếu cho bất kỳ cuộc thăm dò nào, người dùng phải đăng nhập. Chúng tôi sẽ sử dụng email và mật khẩu như phương tiện xác thực cho dự án này. : .env.local NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key Cập nhật thành phần đăng nhập của bạn để xử lý cả đăng nhập và đăng nhập qua email / mật khẩu: import { useState } from "react"; import { createClient } from "@/utils/supabase/component"; const LogInButton = () => { const supabase = createClient(); async function logIn() { const { error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) { setError(error.message); } else { setShowModal(false); } } async function signUp() { const { error } = await supabase.auth.signUp({ email, password, options: { data: { user_name: userName, }, }, }); if (error) { setError(error.message); } else { setShowModal(false); } } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (isLogin) { await logIn(); } else { await signUp(); } }; return ( <> <button onClick={() => setShowModal(true)} className="flex items-center gap-2 p-2 bg-gray-800 text-white rounded-md"> Log In </button> ... </> ); }; export default LogInButton; Ở đây, chúng tôi đang sử dụng Supabase phương pháp để đăng nhập vào một người dùng và phương pháp để đăng ký một người dùng mới với email và mật khẩu của họ. chúng tôi cũng lưu trữ tên người dùng trong trường trong metadata của người dùng. signInWithPassword signUp user_name Bạn cũng có thể sử dụng Để đăng xuất người dùng và chuyển hướng họ: supabase.auth.signOut() import { createClient } from "@/utils/supabase/component"; import { useRouter } from "next/router"; const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => { const router = useRouter(); const supabase = createClient(); const handleLogOut = async () => { await supabase.auth.signOut(); closeDropdown(); router.push("/"); }; return ( ... ); }; export default LogOutButton; Ở đây, chúng tôi đang sử dụng các phương pháp từ Supabase để đăng xuất người dùng và chuyển hướng họ đến trang chủ. signOut Lắng nghe thay đổi trạng thái xác thực của người dùng Listening for changes in the user's authentication state allows us to update the UI based on the user's authentication status. This allows you to: Thì Thì Thì Thì Hiển thị / ẩn các yếu tố UI như nút login / logout Conditionally restrict access to protected pages (like voting or managing polls) Đảm bảo chỉ người dùng được xác thực có thể thực hiện các hành động bị hạn chế Chúng ta sẽ sử dụng để lắng nghe các sự kiện này và cập nhật ứng dụng phù hợp. supabase.auth.onAuthStateChange() Thì In the Layout.tsx file: Track Global Auth State import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Layout = ({ children }: { children: React.ReactNode }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Layout; Giới hạn truy cập vào các trang được bảo vệ Các trang như hoặc , bạn cũng nên lắng nghe thay đổi trạng thái xác thực để ngăn người dùng không xác thực truy cập chúng. poll details poll management Here’s how it looks in : pages/polls/[id].tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); export default Page; Và một mô hình tương tự áp dụng trong , where users should only see their own polls if logged in: pages/polls/manage.tsx import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const Page = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); if (!session?.user) { setLoading(false); } }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... ); }; export default Page; Những mẫu này đảm bảo UI của bạn phản ánh trạng thái xác thực hiện tại của người dùng và tạo thành cơ sở cho các kiểm tra ủy quyền mà chúng tôi sẽ thêm sau. Đối tượng khi gọi Edge Chức năng để xác định xem một người dùng có được phép bỏ phiếu hay quản lý một cuộc thăm dò cụ thể. user checkPermission Xây dựng chức năng của ứng dụng Polling Với Supabase được cấu hình và xác thực làm việc, bây giờ chúng tôi có thể xây dựng chức năng cốt lõi của ứng dụng thăm dò. Thì Thì Tạo các polls mới Fetching and displaying polls in real-time Thực hiện một hệ thống bỏ phiếu Điều này cung cấp cho chúng tôi hành vi ứng dụng cơ bản mà chúng tôi sẽ sớm bảo vệ với các quyền tinh tế. Tạo Polls mới Người dùng phải đăng nhập để tạo các cuộc thăm dò. Mỗi cuộc thăm dò bao gồm một câu hỏi, ngày hết hạn và một tập hợp các tùy chọn. Chúng tôi cũng ghi lại ai đã tạo ra cuộc thăm dò để chúng tôi có thể sau này sử dụng mối quan hệ đó để kiểm soát truy cập. bên trong , fetch the authenticated user, and use Supabase to insert the poll and its options: NewPoll.tsx import React, { useEffect, useState } from "react"; import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const NewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() => { const fetchUser = async () => { const supabase = createClient(); const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (question.trim() && options.filter(opt => opt.trim()).length < 2) { setErrorMessage("Please provide a question and at least two options."); return; } // Create the poll const { data: poll, error: pollError } = await supabase .from("polls") .insert({ question, expires_at: new Date(expiryDate).toISOString(), created_by: user?.id, creator_name: user?.user_metadata?.user_name, }) .select() .single(); if (pollError) { console.error("Error creating poll:", pollError); setErrorMessage(pollError.message); return; } // Create the options const { error: optionsError } = await supabase.from("options").insert( options .filter(opt => opt.trim()) .map(text => ({ poll_id: poll.id, text, })) ); if (!optionsError) { setSuccessMessage("Poll created successfully!"); handleCancel(); } else { console.error("Error creating options:", optionsError); setErrorMessage(optionsError.message); } }; return ( ... ); }; export default NewPoll; Sau đó chúng tôi sẽ gọi một hàm Edge ở đây để gán vai trò “creator” trong Permit.io. Fetching và Displaying Polls Polls are divided into (Vẫn chưa hết hạn) và Bạn có thể lấy chúng bằng cách sử dụng các truy vấn Supabase được lọc theo dấu thời gian hiện tại, và thiết lập đăng ký thời gian thực để phản ánh các thay đổi ngay lập tức. active past Ví dụ từ : pages/index.tsx import { PollProps } from "@/helpers"; import { createClient } from "@/utils/supabase/component"; export default function Home() { const supabase = createClient(); useEffect(() => { const fetchPolls = async () => { setLoading(true); const now = new Date().toISOString(); try { // Fetch active polls const { data: activePolls, error: activeError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .gte("expires_at", now) .order("created_at", { ascending: false }); if (activeError) { console.error("Error fetching active polls:", activeError); return; } // Fetch past polls const { data: expiredPolls, error: pastError } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .lt("expires_at", now) .order("created_at", { ascending: false }); if (pastError) { console.error("Error fetching past polls:", pastError); return; } setCurrentPolls(activePolls); setPastPolls(expiredPolls); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription on the polls table: const channel = supabase .channel("polls") .on( "postgres_changes", { event: "*", schema: "public", table: "polls", }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, []); return ( ... ); } Xem và quản lý các cuộc thăm dò người dùng Here, we are fetching active and past polls from the bảng trong Supabase.Chúng tôi cũng đang thiết lập đăng ký thời gian thực để nghe về những thay đổi trong bảng để chúng tôi có thể cập nhật UI với dữ liệu thăm dò mới nhất. Để phân biệt giữa các cuộc thăm dò hiện tại và trước đây, chúng tôi đang so sánh ngày hết hạn của mỗi cuộc thăm dò với ngày hiện tại. polls polls Cập nhật The trang để lấy và hiển thị chỉ các cuộc thăm dò được tạo bởi người dùng: pages/manage.tsx import { PollProps } from "@/helpers"; const Page = () => { useEffect(() => { if (!user?.id) return; const fetchPolls = async () => { try { const { data, error } = await supabase .from("polls") .select( ` id, question, expires_at, creator_name, created_by, votes (count) ` ) .eq("created_by", user.id) .order("created_at", { ascending: false }); if (error) { console.error("Error fetching polls:", error); return; } setPolls(data || []); } catch (error) { console.error("Unexpected error fetching polls:", error); } finally { setLoading(false); } }; fetchPolls(); // Set up real-time subscription const channel = supabase .channel(`polls_${user.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "polls", filter: `created_by=eq.${user.id}`, }, fetchPolls ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [user]); return ( ... ); }; export default Page; Ở đây, chúng tôi chỉ thu thập các cuộc thăm dò được tạo bởi người dùng và lắng nghe các bản cập nhật thời gian thực trong bảng để UI được cập nhật với dữ liệu khảo sát mới nhất. polls Ngoài ra, cập nhật các thành phần để nếu người dùng đã đăng nhập là người tạo khảo sát, các biểu tượng để chỉnh sửa và xóa khảo sát sẽ được hiển thị cho họ trong khảo sát. PollCard import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const PollCard = ({ poll }: { poll: PollProps }) => { const [user, setUser] = useState<User | null>(null); useEffect(() => { const supabase = createClient(); const fetchUser = async () => { const { data } = supabase.auth.onAuthStateChange((event, session) => { setUser(session?.user || null); setLoading(false); }); return () => { data.subscription.unsubscribe(); }; }; fetchUser(); }, []); return ( ... )} </Link> ); }; export default PollCard; Vì vậy, bây giờ, trên thẻ khảo sát, nếu người dùng đã đăng nhập là người tạo khảo sát, các biểu tượng để chỉnh sửa và xóa khảo sát sẽ được hiển thị cho họ. Thực hiện hệ thống bỏ phiếu Poll Logic bỏ phiếu áp dụng: Thì Thì Thì Thì Only one vote per user per poll Người sáng tạo không thể bỏ phiếu trên các cuộc thăm dò của riêng họ Các phiếu bầu được lưu trữ trong bảng bỏ phiếu Kết quả được hiển thị và cập nhật trong thời gian thực Chúng ta hãy chia nhỏ cách điều này hoạt động trong Thành phần : ViewPoll.tsx Chúng tôi cần ID của người dùng hiện tại để xác định quyền bỏ phiếu và ghi lại phiếu bầu của họ. Fetch the Logged-In User import { createClient } from "@/utils/supabase/component"; import { User } from "@supabase/supabase-js"; const ViewPoll = () => { const [user, setUser] = useState<User | null>(null); const supabase = createClient(); useEffect(() const fetchUser = async () => { const { data: { user }, } = await supabase.auth.getUser(); setUser(user); }; fetchUser(); }, []); Một khi chúng tôi có người dùng, chúng tôi thu thập: Load Poll Details and Check Voting Status Thì Thì Bản thân cuộc thăm dò (bao gồm các lựa chọn và đếm phiếu) Nếu người dùng này đã bỏ phiếu Chúng tôi cũng gọi chúng trở lại sau này trong các bản cập nhật thời gian thực. useEffect(() => { if (!user) { return; } const checkUserVote = async () => { const { data: votes } = await supabase .from("votes") .select("id") .eq("poll_id", query.id) .eq("user_id", user.id) .single(); setHasVoted(!!votes); setVoteLoading(false); }; const fetchPoll = async () => { const { data } = await supabase .from("polls") .select( ` *, options ( id, text, votes (count) ) ` ) .eq("id", query.id) .single(); setPoll(data); setPollLoading(false); checkUserVote(); }; fetchPoll(); Listen for Real-Time Updates Chúng tôi đồng ý với những thay đổi trong table, scoped to this poll. When a new vote is cast, we fetch updated poll data and voting status. votes const channel = supabase .channel(`poll-${query.id}`) .on( "postgres_changes", { event: "*", schema: "public", table: "votes", filter: `poll_id=eq.${query.id}`, }, () => { fetchPoll(); checkUserVote(); } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [query.id, user]); Handle the Vote Submission Nếu người dùng không bỏ phiếu và được phép bỏ phiếu (chúng tôi sẽ thêm một kiểm tra quyền sau), chúng tôi chèn phiếu của họ. const handleVote = async (optionId: string) => { if (!user) return; try { const { error } = await supabase.from("votes").insert({ poll_id: query.id, option_id: optionId, user_id: user.id, }); if (!error) { setHasVoted(true); } } catch (error) { console.error("Error voting:", error); } }; Chúng tôi tính tổng số phiếu bầu và đếm ngược đến thời gian hết hạn, sau đó bạn có thể sử dụng điều này để hiển thị thanh tiến độ hoặc số liệu thống kê. Display the Poll Results if (!poll || pollLoading || voteLoading) return <div>Loading...</div>; // 6. calculate total votes const totalVotes = calculateTotalVotes(poll.options); const countdown = getCountdown(poll.expires_at); return ( ... ); }; export default ViewPoll; Với thiết lập này tại chỗ, hệ thống bỏ phiếu của bạn là đầy đủ chức năng. nhưng ngay bây giờ, bất cứ ai đã đăng nhập có thể kỹ thuật thử để bỏ phiếu - ngay cả trong cuộc thăm dò của riêng họ. Sử dụng và để thực thi các quy tắc đó. authorization checks Permit.io Supabase Edge Functions Trước khi chúng ta làm điều đó, trước tiên chúng ta hãy nhìn vào loại lớp ủy quyền mà chúng ta sẽ thực hiện. Understanding ReBAC (Relationship-Based Access Control) Supabase xử lý xác thực và quyền cấp hàng cơ bản tốt, nhưng nó không hỗ trợ các quy tắc phức tạp như: Thì Thì Thì Ngăn chặn người dùng bỏ phiếu trong các cuộc thăm dò của riêng họ Assigning per-resource roles (like “creator” for a specific poll) Quản lý truy cập thông qua các chính sách bên ngoài Để hỗ trợ các loại quyền dựa trên mối quan hệ này, chúng tôi sẽ triển khai ReBAC với Permit.io. Thay vì chỉ dựa vào vai trò hoặc thuộc tính (như trong RBAC hoặc ABAC), ReBAC xác định truy cập bằng cách đánh giá cách người dùng kết nối với tài nguyên mà họ đang cố gắng truy cập. Relationship-Based Access Control (ReBAC) Kiểm soát truy cập dựa trên mối quan hệ (ReBAC) Trong hướng dẫn này, chúng tôi áp dụng ReBAC cho một ứng dụng bỏ phiếu: Thì Thì Thì Thì Một người dùng đã tạo một cuộc thăm dò là người duy nhất có thể quản lý (sửa / xóa) nó Người dùng không thể bỏ phiếu trong cuộc thăm dò của chính họ Người dùng được xác thực khác có thể bỏ phiếu một lần cho mỗi cuộc thăm dò Bằng cách mô hình hóa các mối quan hệ này trong Permit.io, chúng tôi có thể xác định các quy tắc truy cập hạt mịn vượt ra ngoài Bảo mật cấp Row (RLS) tích hợp của Supabase. chúng tôi sẽ thực thi chúng trong thời gian chạy bằng cách sử dụng các hàm Supabase Edge và công cụ chính sách của Permit. Để biết thêm về ReBAC, hãy kiểm tra của Tài liệu ReBAC của Permit.io Thiết kế Access Control Đối với ứng dụng Polling của chúng tôi, chúng tôi sẽ xác định: Thì Thì Thì Một tài nguyên với các hành động cụ thể về tài nguyên: khảo sát: tạo, đọc, xóa, cập nhật. Hai vai trò để cấp cấp cấp độ quyền dựa trên mối quan hệ của người dùng với các tài nguyên: xác thực: Có thể thực hiện hành động tạo và đọc trong cuộc thăm dò. Không thể xóa hoặc cập nhật hành động trong cuộc thăm dò. người tạo: Có thể tạo, đọc, xóa và cập nhật hành động trong cuộc thăm dò. Có thể thực hiện hành động đọc và tạo trong phiếu bầu. Không thể sử dụng tạo trên cuộc thăm dò của riêng họ. Cài đặt Permit.io Chúng ta hãy đi qua việc thiết lập mô hình ủy quyền trong Permit. Thì Thì Thì Thì Tạo một dự án mới trong Permit.io Tên nó giống như supabase-polling Định nghĩa tài nguyên khảo sát Đi đến chính sách → tab tài nguyên Nhấp vào "Tạo tài nguyên" Tên nó khảo sát, và thêm các hành động: đọc, tạo, cập nhật, xóa Kích hoạt ReBAC cho tài nguyên Trong phần “Tùy chọn ReBAC”, xác định các vai trò sau: authenticated creator Click Save Tạo một dự án mới Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: Chú ý: , , Điều này là không cần thiết cho tutorial này. admin editor user Define access policies Go to the tab Policy → Policies Use the visual matrix to define: can and polls authenticated read create can , , and polls creator read update delete (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately Thêm các trường hợp tài nguyên Đi đến Thư mục → Các trường hợp Thêm ID cuộc thăm dò cá nhân làm các trường hợp tài nguyên (bạn sẽ tự động hóa điều này sau khi các cuộc thăm dò mới được tạo) Gán vai trò cho người dùng mỗi cuộc thăm dò (ví dụ: user123 là người tạo cuộc thăm dò456) Cấu trúc này cho phép chúng tôi viết các quy tắc truy cập linh hoạt và thực thi chúng cho mỗi người dùng, cho mỗi cuộc thăm dò. Bây giờ chúng tôi đã hoàn thành cài đặt ban đầu trên bảng điều khiển Cho phép, hãy sử dụng nó trong ứng dụng của chúng tôi. đến dự án Supabase của chúng tôi thông qua Edge Functions mà: Cho phép.io Thì Thì Thì Thì Sync người dùng mới Chọn Creator Roles Kiểm tra truy cập theo yêu cầu Setting up Permit in the Polling Application Permit cung cấp nhiều cách để tích hợp với ứng dụng của bạn, nhưng chúng tôi sẽ sử dụng Container PDP cho hướng dẫn này. Bạn phải lưu trữ container trực tuyến để truy cập nó trong các chức năng Supabase Edge. Một khi bạn đã lưu nó, hãy lưu URL cho container của bạn. Đường sắt.com Nhận phím API cho phép của bạn bằng cách nhấp vào "Projects" trong thanh bên của bảng điều khiển cho phép, điều hướng đến dự án bạn đang làm việc, nhấp vào ba điểm và chọn "Copy API Key". Tạo Supabase Edge Function API cho phép Chúng tôi sẽ sử dụng chúng để thực thi các quy tắc ReBAC của chúng tôi trong thời gian chạy bằng cách kiểm tra xem người dùng có được phép thực hiện các hành động cụ thể trong các cuộc thăm dò hay không. Supabase Edge Functions Create Functions in Supabase Bắt đầu Supabase trong dự án của bạn và tạo ba chức năng khác nhau bằng cách sử dụng command. These will be the starting point for your functions: supabase functions new npx supabase init npx supabase functions new syncUser npx supabase functions new updateCreatorRole npx supabase functions new checkPermission Điều này sẽ tạo ra một Folder trong thư mục cùng với các điểm cuối. Bây giờ, hãy viết mã cho mỗi điểm cuối. functions supabase Đánh giá của người dùng để xác định vị trí ( ) syncUser.ts This function listens for Supabase’s Khi một người dùng mới đăng nhập, chúng tôi đồng bộ danh tính của họ với Permit.io và gán cho họ vai trò SIGNED_UP authenticated import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', } // Supabase Edge Function to sync new users with Permit.io Deno.serve(async (req) => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { event, user } = await req.json(); // Only proceed if the event type is "SIGNED_UP" if (event === "SIGNED_UP" && user) { const newUser = { key: user.id, email: user.email, name: user.user_metadata?.name || "Someone", }; // Sync the user to Permit.io await permit.api.createUser(newUser); await permit.api.assignRole({ role: "authenticated", tenant: "default", user: user.id, }); console.log(`User ${user.email} synced to Permit.io successfully.`); } // Return success response return new Response( JSON.stringify({ message: "User synced successfully!" }), { status: 200, headers: corsHeaders }, ); } catch (error) { console.error("Error syncing user to Permit: ", error); return new Response( JSON.stringify({ message: "Error syncing user to Permit.", "error": error }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } }); Chức năng của người sáng tạo ( ) updateCreatorRole.ts Sau khi người dùng tạo một cuộc thăm dò, chức năng này được gọi là: Thì Thì Thì Đồng bộ khảo sát như là một phiên bản tài nguyên mới của Permit.io Resource này được tạo ra bởi người dùng: Resource: Assign the creator role for that poll status "jsr:@supabase/functions-js/edge-runtime.d.ts"; import {Permit } from "npm:permitio"; return new corsHeaders = { 'Access-Control-message-Allow-Origin': "*", 'Access-Control-Allow-Headers' type: 'Authorization, x-client-info error, apikey error, apikey error, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 'Errubate', 'Erstrole assignator(async (req) => 'const = permit new Assignator' role Checking Permissions ( ) checkPermission.ts Chức năng này hoạt động như một cửa sổ – nó kiểm tra xem người dùng có được phép thực hiện một hành động nhất định hay không ( , , , Trong một cuộc thăm dò cụ thể. create read update delete import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Authorization, x-client-info, apikey, Content-Type", "Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE", }; Deno.serve(async req => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { userId, operation, key } = await req.json(); // Validate input parameters if (!userId || !operation || !key) { return new Response( JSON.stringify({ error: "Missing required parameters." }), { status: 400, headers: { "Content-Type": "application/json" } } ); } // Check permissions using Permit's ReBAC const permitted = await permit.check(userId, operation, { type: "polls", key, tenant: "default", // Include any additional attributes that Permit needs for relationship checking attributes: { createdBy: userId, // This will be used in Permit's policy rules }, }); return new Response(JSON.stringify({ permitted }), { status: 200, headers: corsHeaders, }); } catch (error) { console.error("Error checking user permission: ", error); return new Response( JSON.stringify({ message: "Error occurred while checking user permission.", error: error, }), { status: 500, headers: { "Content-Type": "application/json" } } ); } }); Local Testing Bắt đầu Supabase dev server của bạn để kiểm tra các chức năng tại địa phương: npx supabase start npx supabase functions serve Sau đó bạn có thể nhấn các chức năng của bạn tại: <http://localhost:54321/functions/v1/><function-name> Ví dụ : <http://localhost:54321/functions/v1/checkPermission> Tích hợp kiểm tra ủy quyền trong UI Now that we’ve created our authorization logic with Permit.io and exposed it via Supabase Edge Functions, it’s time to enforce those checks inside the app’s components. Trong phần này, chúng tôi sẽ cập nhật các thành phần UI chính để gọi các chức năng đó và điều kiện cho phép hoặc chặn các hành động của người dùng như bỏ phiếu hoặc quản lý các cuộc thăm dò dựa trên kiểm tra quyền. Chọn Creator Role After Poll Creation NewPoll.tsx Đánh giá.tsx Sau khi tạo một cuộc thăm dò và lưu nó vào Supabase, chúng tôi gọi Chức năng 2 : updateCreatorRole Thì Thì Thì Sync the new poll as a resource in Permit.io Thay vào đó, chúng tôi sẽ cung cấp cho người dùng các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan đến các thông tin liên quan : Hạn chế bỏ phiếu dựa trên giấy phép ViewPoll.tsx Trước khi cho phép người dùng bỏ phiếu trong một cuộc thăm dò, chúng tôi gọi chức năng để xác minh rằng họ có Giấy phép trên Tài nguyên.Đây là cách chúng tôi thực thi quy tắc: checkPermission create votes “A creator cannot vote on their own poll.” Check voting permission: const [canVote, setCanVote] = useState(false); useEffect(() => { const checkPermission = async () => { if (!user || !query.id) return; try { const response = await fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "create", key: query.id, }), }); const { permitted } = await response.json(); setCanVote(permitted); } catch (error) { console.error("Error checking permission:", error); setCanVote(false); } }; checkPermission(); }, [user, query.id]); Disable vote buttons if user isn’t allowed: <button onClick={() => handleVote(option.id)} disabled={!user || !canVote}} className="w-full text-left p-4 rounded-md hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> {option.text} </button> Show a message if the user is not allowed to vote: {user && !canVote && ( <p className="mt-4 text-gray-600">You cannot vote on your own poll</p> )} : Kiểm soát truy cập vào Edit/Delete PollCard.tsx Chúng tôi cũng hạn chế các hành động quản lý khảo sát (sửa và xóa) bằng cách kiểm tra xem người dùng có hoặc cho phép trong cuộc thăm dò này. update delete Check management permissions: const [canManagePoll, setCanManagePoll] = useState(false); useEffect(() => { const checkPollPermissions = async () => { if (!user || !poll.id) return; try { // Check for both edit and delete permissions const [editResponse, deleteResponse] = await Promise.all([ fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "update", key: poll.id, }), }), fetch("/api/checkPermission", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user.id, operation: "delete", key: poll.id, }), }), ]); const [{ permitted: canEdit }, { permitted: canDelete }] = await Promise.all([editResponse.json(), deleteResponse.json()]); // User can manage poll if they have either edit or delete permission setCanManagePoll(canEdit || canDelete); } catch (error) { console.error("Error checking permissions:", error); setCanManagePoll(false); } }; checkPollPermissions(); }, [user, poll.id]); Conditionally show management buttons: Thay thế : {user?.id === poll?.created_by && ( Với : {canManagePoll && ( <div className="flex justify-start gap-4 mt-4"> <button type="button" onClick={handleEdit}> </button> <button type="button" onClick={handleDelete}> </button> </div> )} Thử nghiệm Integration Một khi tích hợp, bạn sẽ thấy các hành vi sau đây trong ứng dụng: Thì Thì Thì Thì Người dùng đã đăng xuất có thể xem các cuộc thăm dò nhưng không thể tương tác Người dùng được xác thực có thể bỏ phiếu cho các cuộc thăm dò mà họ không tạo Người sáng tạo không thể bỏ phiếu trên các cuộc thăm dò của riêng họ Chỉ những người sáng tạo mới thấy các tùy chọn chỉnh sửa/xóa trong các cuộc thăm dò của họ Bạn nên có thể xem các thay đổi của ứng dụng bằng cách đi đến trình duyệt. Trên màn hình chính, người dùng có thể xem danh sách các cuộc thăm dò đang hoạt động và trước đó, cho dù họ đã đăng nhập hay không. Tuy nhiên, khi họ nhấp vào một cuộc thăm dò, họ sẽ không thể xem chi tiết cuộc thăm dò hoặc bỏ phiếu về nó. Một khi đã đăng nhập, người dùng có thể xem chi tiết của cuộc thăm dò và bỏ phiếu cho nó. Tuy nhiên, nếu người dùng là người tạo cuộc thăm dò, họ sẽ không thể bỏ phiếu cho nó. Họ sẽ thấy một thông báo cho thấy họ không thể bỏ phiếu cho cuộc thăm dò của riêng họ. Họ cũng sẽ được phép quản lý bất kỳ cuộc thăm dò nào họ tạo. Kết luận Trong hướng dẫn này, chúng tôi đã khám phá cách thực hiện Trong thế giới thực Ứng dụng Supabase authentication and authorization Next.js Chúng tôi bắt đầu bằng cách thiết lập để đăng nhập và đăng nhập, tạo một sơ đồ quan hệ với Row Level Security, và thêm logic ủy quyền năng động bằng cách sử dụng . With the help of và a , chúng tôi thực thi kiểm tra giấy phép trực tiếp từ frontend. Supabase Auth ReBAC Supabase Edge Functions Policy Decision Point (PDP) bằng cách kết hợp Với kiểm soát truy cập linh hoạt, chúng tôi có thể: Supabase Auth Thì Thì Thì Thì Thì Xác thực người dùng qua email và mật khẩu Giới hạn việc bỏ phiếu và quản lý thăm dò cho người dùng được ủy quyền Ngăn chặn các nhà sáng tạo bỏ phiếu trên các cuộc thăm dò của riêng họ Gán và đánh giá vai trò người dùng dựa trên mối quan hệ với dữ liệu Thiết lập này cung cấp cho bạn một nền tảng có thể mở rộng cho việc xây dựng các ứng dụng đòi hỏi cả xác thực và ủy quyền tinh tế. Đọc thêm Thì Thì Thì Thì Thì Permit.io Hướng dẫn sử dụng ReBAC Giấy phép + Nhà cung cấp xác thực Permit Elements: Embedded UI for Role Management Lọc dữ liệu với Permit Kiểm toán Logs Có câu hỏi không? tham gia cùng chúng tôi , nơi hàng trăm nhà phát triển đang xây dựng và thảo luận về ủy quyền. Slack community Slack cộng đồng